<template>
  <div class="tree-d3">
    own-tree-d3
    <div class="tree-box" :style="{'height': treeBoxHeight + 'px'}">
      <div class="tree-container" :style="{'height': treeHeight + 10 + 'px'}" :id="id">
        <svg class="svg-tree">
          <g class="g-container"></g>
        </svg>
        <div class="current-node" 
          v-if="false"
          :style="{top: currentNode.x + 'px', left: currentNode.y + 'px'}">
          {{currentNode.x}}
        </div>  
      </div>     
    </div>
    <div class="change-zoom">
      <div class="out cz-icon" @click="changeSvg('out')">+</div>
      <div class="in cz-icon" @click="changeSvg('in')">-</div>
    </div>
    <div class="tip-info">
      <div class="state-item" v-for="(v, i) in stateArr" :key="i">
        <span>state{{i+1}}:</span><i :style="{'background': v.c}"/>
      </div>
    </div>
  </div>
</template>
 
<script>
import * as d3 from 'd3'
import { filterNode, getAllLastChild } from '@/utils/common'
export default {
  props: {
    // w: 170  140  100  96
    dataTree: {
      type: Object,
      default() {
        return {
          name: '中国',
          children: [
            {
              name: '北京',
              children: [
                {
                  name: '海淀',
                  children: [
                    {name: '香山'}
                  ]
                }, 
                {name: '朝阳'},
                {name: '丰台'}
              ]
            },{
              name: '河北',
              children: [
                {name: '石家庄'}, 
                {name: '正定'},
              ]
            }, {
              name: '上海'
            }
          ]
        }
      }
    },
    stateArr: {
      type: Array,
      default() {
        return [
          {n: 'state1', c: '#B8B8B8'},
          {n: 'state2', c: '#95DF32'},
          {n: 'state3', c: '#56A1FF'},
          {n: 'state4', c: '#B665DB'},
          {n: 'only', c: '#f76d6d'}
        ]
      }
    }
  },
  data () {
    return {
      // dataTree: {},
      id: 'treeD3Own',
      index: 0,
      duration: 750,
      root: null,
      nodes: [],
      links: [],
      dTreeData: null,
      transform: {
        scale: 0.8
      },
      width: 0,
      height: 0,
      margin: { top: 30, right: 120, bottom: 20, left: 240 },
      currentNode: {
        x: 0,
        y: 0,
        opacity: 0
      },
      isInit: true,
      node: null, // getNode
    }
  },
  computed: {
    treeHeight() {
      let len = getAllLastChild(this.dataTree.children).length
      return len * 40
      // return 500
    },
    treeBoxHeight() {
      // let len = getAllLastChild(this.dataTree.children).length
      return 800
    },
    treemap () {
      return d3.tree().size([this.height, this.width])
    }
  },
  mounted() {
    this.init()
    setTimeout( () => {
      this.update(this.root)
    }, 1000)
  },
  methods: {
    // scale
    init() {
      this.getOnlyState()
      //创建svg画布
      this.width = document.getElementById(this.id).clientWidth
      this.height = document.getElementById(this.id).clientHeight
      // console.log(this.height)
      const svg = d3.select(this.$el).select('svg.svg-tree')
          .attr('width', this.width)
          .attr('height', this.treeHeight)
      const transform = d3.zoomIdentity.translate(this.margin.left, this.margin.top).scale(this.transform.scale)    
      const container = svg.select('g.g-container')
      // init zoom behavior, which is both an object and function
      this.zoom = d3.zoom()
          .scaleExtent([1, 1]) // 比例极限
          .on('zoom', this.zoomed)
      container.transition().duration(750).call(this.zoom.transform, transform)
      svg.call(this.zoom)
      this.root = this.getRoot()
      // console.log(this.root)
    },

    // zoom: 'in' | 'out'
    changeSvg(zoom) {
      zoom === 'out' ? 
        (this.transform.scale = this.transform.scale > 3/2 ? this.transform.scale : this.transform.scale * 1.1) : 
        (this.transform.scale = this.transform.scale < 1/2 ? this.transform.scale : this.transform.scale * 0.9)
      this.init()
    },

    // 获取唯一'only' state为5的值
    getOnlyState() {
      // let only = this.getTreeChild(this.dataTree)
      // console.log(filterNode(this.dataTree.children))
      // console.log([this.dataTree])

      // 这种改变children|_children 的方法 -> error
      // let relationArr = this.findIndexArray([this.dataTree], '1212483237', [])
      // this.changeTree([this.dataTree], relationArr)
    },
    // 查找与目标元素及父元素
    findIndexArray(data, id, indexArray) {
      let arr = Array.from(indexArray)
      for (let i = 0, len = data.length; i < len; i++) {
        arr.push(data[i]['Account ID'])
        if (data[i]['Account ID'] === id) {
          return arr
        }
        let children = data[i].children
        if (children && children.length) {
          let result = this.findIndexArray(children, id, arr)
          if (result) return result
        }
        arr.pop()
      }
      return false
    },
    changeTree(data, relationArr) {
      // console.log(relationArr)
      if(relationArr) {
        for (let i = 0; i < data.length; i++) {
          if (relationArr.indexOf(data[i]['Account ID']) > -1) {
            data[i]['_children'] = null
          } else {
            data[i]['_children'] = data[i].children
            data[i].children = null            
          }
          let children = data[i].children
          if (children && children.length) {
            let result = this.changeTree(children, relationArr)
            if(result) {
                return result
            }
          }
          let _children = data[i]._children
          if (_children && _children.length) {
            let result = this.changeTree(_children, relationArr)
            if(result) {
              return result
            }
          }
        }
      }
      
      return false
    },

    getNode(arr, nodeId) {
      var parentNode = null
      //1.第一层 root 深度遍历整个arr
      for (var i = 0; i < arr.length; i++) {
        if (this.node) {
          break;
        }
        var obj = arr[i];
        parentNode = obj;
        if (!obj || !obj["Account ID"]) {
          continue;
        }
        if (obj["Account ID"] == nodeId) {
          this.node = obj;
          break;
        } else {
          if (obj.children) {
            this.getNode(obj.children, nodeId);
          } else {
            continue;
          }
        }
      }
      if (this.node == null) {
        parentNode = null;
      }
      if (parentNode !== null) {
        // console.log(parentNode)
        if(parentNode.hasOwnProperty('children')) {
          // parentNode['_children'] = parentNode['children']
          // parentNode['children'] = []
          // parentNode["collapsed"] = false;
          if(parentNode["Account ID"] == nodeId){
            // parentNode['children'] = parentNode['_children']
            // parentNode['_children'] = []
            // parentNode["collapsed"] = true;
          }
        }
      }
    },
    // @description 获取构造根节点
    getRoot () {
      // this.dataTree['name'] = this.dataTree['Company Name'].length > 18 ?
      //   this.dataTree['Company Name'].substr(0,16) + '...' :
      //   this.dataTree['Company Name']
      // console.log(this.dataTree)
      // this.changeTree(this.dataTree.children)
      // this.getNode(this.dataTree.children, '1212458269')
      let root = d3.hierarchy(this.dataTree, d => {
        return d.children
      })
      // root.x0 = this.height / 2
      root.x0 = this.treeHeight / 2
      root.y0 = 0
      // console.log(root)
      return root
    },
    // 点击该节点
    clickNode (d) {
      // console.log(d)
      if (!d._children && !d.children)
        return
      if (d.children) {
        this.$set(d, '_children', d.children)          
        d.children = null
      } else {
        this.$set(d, 'children', d._children)
        d._children = null
      }
      this.$nextTick(
        () => {
          this.update(d)
          this.isInit = false                
        }
      )
    },
    hoverNode(d) {
      // console.log(d)
      // this.$emit('hoverNode', d) 
    },
    leftClick (d) {
      // console.log(d3.event)
      if(d3.event.buttons === 0) {
        this.currentNode = d
        console.log(this.currentNode)
        // console.log("左击11111:")
        // console.log(d)
        this.$emit('leftClick', d)  
      }
    },
    rightClick (d) {
      if(d3.event.buttons === 2) {
        this.$emit('rightClick', d)
        // console.log("右击22222:"+d)
      }
    },
    diagonal (s, d) {
      return `M ${s.y} ${s.x}
              C ${(s.y + d.y) / 2} ${s.x},
              ${(s.y + d.y) / 2} ${d.x},
              ${d.y} ${d.x}`
    },
    // @description 获取构造的node数据和link数据
    getNodesAndLinks () {
      // treemap generate new x、y coordinate according to root node, 
      // so can‘t use computed propter of vue
      this.dTreeData = this.treemap(this.root)
      this.nodes = this.dTreeData.descendants()
      this.links = this.dTreeData.descendants().slice(1)
    },
    // @description 数据与Dom进行绑定
    update (source) {
      this.getNodesAndLinks()
      this.nodes.forEach(d => { 
        d.y = d.depth * 180
      })
      // Nodes section 
      // Update the nodes...
      const svg = d3.select(this.$el).select('svg.svg-tree')
      const container = svg.select('g.g-container')
      let node = container.selectAll('g.node')
        .data(this.nodes, d => {
          if(d.depth > 0 && d.children && this.isInit ) {
            // this.clickNode(d)
          }
          // if(d.data['Account ID'] === "1212289407") {
          //   // console.log(d)
          //   this.clickNode(d)
          //   if(d.parent) {
          //     this.clickNode(d.parent)
          //   }            
          // }
          return d.name = d['Company Name'] || d.id || (d.id = ++this.index)
        }) 
      // Enter any new sources at the parent's previous position.
      let nodeEnter = node.enter().append('g')
        .attr('class', 'node')
        .on('hover', this.hoverNode)
        .on('contextmenu', function() {
          d3.event.preventDefault()
        })
        .on('mousedown', this.rightClick)
        .on('click', this.leftClick)
        .attr('transform', d => {
          return 'translate(' + source.y0 + ',' + source.x0 + ')'
        })
      
      let rectConfig = {
          'x': (d) => { return d.children || d._children ? -145 : 10 },
          'y': -12,
          'width': 135,
          'height': 24,
          'rx': 10
        }
      let circleConfig = {
          'r': 10,
          'fill': 'white',
          'stroke': 'blue',
          'stroke-width': 1
        };

      // console.log(this.stateArr['3'].c)
      let stateArr = this.stateArr
      nodeEnter.append("rect")
        .attr(rectConfig)
        // .attr('x', function(d) {
        //   return d.children || d._children ? -80 : 10
        // })
        // .attr('y', -12)
        // .attr('width', 70)
        // .attr('height', 24)
        // .attr('rx', 10)
        .style('fill', function(d) {
          // console.log(this.stateArr[d.data['state']-1].c)
          return d.data['state'] === null ? '#DAEAFF' : stateArr[d.data['state']-1].c
        })

      nodeEnter.append("circle")
        .attr(circleConfig)
        // .attr("r", 10)
        .style("fill", function(d) { return d.children || d._children ? "lightsteelblue" : "#fff"; })
        .on('click', this.clickNode)

      // https://developer.mozilla.org/zh-CN/docs/Web/SVG/Element/text
      nodeEnter.append("text")
        .attr("x", function(d) { return d.children || d._children ? -137 : 18; })
        .attr("dy", ".35em")
        .attr("font-size", '11')
        .attr("text-anchor", "start")
        // .attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
        // .text(function(d) { return d.data.name })
        .text(function(d) { 
          let name = d.data['Company Name']
          d['name'] = name
          d.data['Company Name'] = name.length > 18 ?
            name.substr(0,16) + '...' :
            name
          return d.data['Company Name'] 
        })
        .style("fill-opacity", 1e-6);
      // Transition nodes to their new position.
      let nodeUpdate = nodeEnter.merge(node)
        .transition()
        .duration(this.duration)
        .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });

      nodeUpdate.select("rect")
        .attr('stroke', 'steelblue')
        .attr('stroke-width', 1)
        .style('cursor', 'pointer')
      
      nodeUpdate.select("circle")
        .attr(circleConfig)
        // .attr("r", 10)
        // .attr("fill", "white")
        // .attr("stroke", "blue")
        // .attr("stroke-width", 1)
        .style("fill", function(d) { return d.children || d._children ? "lightsteelblue" : "#fff"; });

      nodeUpdate.select("text")
        .style('cursor', 'pointer')
        .style("fill-opacity", 1);

      // Transition exiting nodes to the parent's new position.
      let nodeExit = node.exit()
        .transition()
        .duration(this.duration)
        .attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
        .remove();

      nodeExit.select("circle")
        .attr("r", 1e-6);

      nodeExit.select("text")
        .style("fill-opacity", 1e-6)

      // Links section 
      // Update the links…
      let link = container.selectAll('path.link')
        .data(this.links, d => { return d.id })

      // Enter any new links at the parent's previous position.
      let linkEnter = link.enter().insert("path", "g")
        .attr("class", "link")
        .attr("d", d => {
            let o = {x: source.x0, y: source.y0};
            return this.diagonal(o, o)
        })
        .attr("fill", 'none')
        .attr("stroke-width", 1)
        .attr('stroke', '#ccc')
      // Transition links to their new position.
      let linkUpdate = linkEnter.merge(link)
      linkUpdate.transition()
        .duration(this.duration)
        .attr('d', d => { return this.diagonal(d, d.parent) })

      // Transition exiting nodes to the parent's new position.
      link.exit().transition()
        .duration(this.duration)
        .attr("d", d => {
            let o = {x: source.x, y: source.y};
            return this.diagonal(o, o)
        })
        .remove();

      // Stash the old positions for transition.
      this.nodes.forEach(d => {
        d.x0 = d.x
        d.y0 = d.y
      })
    },
    // @description 控制容器transform
    zoomed () {
      d3.select(this.$el).select('g.g-container').attr('transform', d3.event.transform)
    }
  }
}
</script>
 
<style scoped lang="scss">
.tree-d3 {
  .change-zoom{
    position: absolute;
    top: 50px;
    right: 50px;
    z-index: 5;
    .cz-icon {
      width: 30px;
      height: 50px;
      line-height: 50px;
      text-align: center;
      font-size: 30px;
      cursor: pointer;
    }
  }
  .tip-info{
    position: absolute;
    top: 50px;
    left: 50px;
    z-index: 5;
    display: flex;
    flex-flow: column nowrap;
    .state-item{
      line-height: 30px;
      display: flex;
      flex-flow: row wrap;
      align-items: center;
      span{font-size: 12px;}
      i{
        margin-left: 5px;
        display: inline-block;
        width: 7px;
        height: 7px;
      }
    }
  }
}
.tree-box{
  position: relative;
  overflow: auto;
}
.tree-container{
  width: 100%;
  // height: 1000px;
  // overflow: auto;
  position: relative;
  .current-node{
    position: absolute;
    z-index: 100;
    width: 200px;
    height: 100px;
    background: #FFF4E5;
    border: 1px solid #E4C3A5;
    border-radius: 3px 3px 3px 0 0 0 3px;
    border-radius: 3px 3px 3px 0px 0px 0px 3px;
  }
  .svg-tree{
    // .g-container{

    // }
    .node {
      cursor: pointer;
    }

    .node circle {
      fill: #fff;
      stroke: steelblue;
      stroke-width: 1.5px;
    }
    .node rect {
      fill: #2990ca;
      stroke: steelblue;
      stroke-width: 1.5px;
    }

    .node text {
      font: 18px sans-serif;
    }

    .link {
      fill: none;
      stroke: #ccc;
      stroke-width: 1.5px;
    }
  }
}
</style>